package org.unit.testmgmt.generator;

import java.util.HashSet;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMemberValuePair;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.unit.testmgmt.tml.UTMElements;

/**
 * The default test-suites-generator
 * 
 * @author Robert Streng
 *
 */
public class TestSuitesGenerator implements ITestSuitesGenerator {

	private static final String TESTSUITE_PREFIX = "TestSuite";

	@Override
	public boolean generateTestSuites(UTMElements utmElements)
			throws CoreException {
		IJavaProject testProject = utmElements.getProjects().getTestProject();
		IPackageFragment testPackage = utmElements.getClassesAndPackages()
				.getTestPackage();
		String testClassName = utmElements.getClassesAndPackages()
				.getTestClassName();
		
		HashSet<String> childTestSuites = new HashSet<String>();
		
		boolean testSuiteExists = generateTestSuites(testProject, testPackage, testClassName, childTestSuites);

		if (testSuiteExists) {
			return true;
		}
 		
		IJavaElement parent = testPackage.getParent();
		IPackageFragmentRoot packageRoot;
		if (parent instanceof IPackageFragmentRoot) {
			packageRoot = (IPackageFragmentRoot) parent;

			IPath parentPackage = new Path(testPackage.getElementName().replace(".", "/"));
			if ("test".equals(parentPackage.lastSegment())) {
				parentPackage = parentPackage.removeLastSegments(2);
				while ((parentPackage != null && !parentPackage.isEmpty() && !parentPackage
						.isRoot())) {
					parentPackage = parentPackage.append("test");
					testPackage = packageRoot.getPackageFragment(parentPackage
							.toString().replace("/", "."));
					generateTestSuites(testProject, testPackage, null, childTestSuites);
					parentPackage = parentPackage.removeLastSegments(2);
				}
				
			}
			else {
				parentPackage = parentPackage.removeLastSegments(1);
				while ((parentPackage != null && !parentPackage.isEmpty() && !parentPackage
						.isRoot())) {
					testPackage = packageRoot.getPackageFragment(parentPackage
							.toString().replace("/", "."));
					generateTestSuites(testProject, testPackage, null, childTestSuites);
					parentPackage = parentPackage.removeLastSegments(1);
				}
			}
			
			if (parentPackage.isEmpty()) {
				parentPackage = parentPackage.append("base").append("test");
				
				if (parentPackage != null) {
					testPackage = packageRoot.getPackageFragment(parentPackage.toString().replace("/", "."));
					generateTestSuites(testProject, testPackage, null, childTestSuites);
				}
			}
		}

		return false;
	}

	/**
	 * Generates the test suites.
	 * 
	 * @param testProject
	 * @param testPackage
	 * @param newTestSuiteElementName
	 * @param childTestSuites
	 * @return true if test test suite already exists
	 * @throws CoreException
	 */
	private boolean generateTestSuites(IJavaProject testProject,
			IPackageFragment testPackage, String newTestSuiteElementName, HashSet<String> childTestSuites) throws CoreException {
		boolean exists = false;

		if (testPackage == null || !testPackage.exists()) {
			return exists;
		}

		String testSuiteName = TESTSUITE_PREFIX;
		
		ICompilationUnit testSuite;
		ICompilationUnit[] testClasses;

		testSuite = testPackage.getCompilationUnit(TESTSUITE_PREFIX + ".java");
		testClasses = testPackage.getCompilationUnits();

		if (testClasses.length == 0 && !"base".equals(testPackage.getPath().lastSegment())) {
			return exists;
		}

		// create new source
		IType testSuiteFrame = createTestSuiteFrame(testSuiteName, testSuite, testClasses, newTestSuiteElementName, childTestSuites);
		if (testSuiteFrame == null) {
			return true;
		}

		// make source beautiful
		String source = GeneratorUtils.organizeImports(testSuite);
		source = GeneratorUtils.formatSource(source);

		// create new compilation-unit
		ICompilationUnit cu = testPackage.createCompilationUnit(testSuite.getElementName(), source, true, null);
		cu.save(null, false);
		
		// clear child test-suites
		childTestSuites.clear();
		// add generated test-suite
		childTestSuites.add(testPackage.getElementName() + "." + TESTSUITE_PREFIX + ".class");
		
		return exists;
	}

	/**
	 * Creates the test suite frame.
	 * 
	 * @param testSuiteName
	 * @param testSuite
	 * @param testClasses
	 * @param newTestSuiteElementName
	 * @param generatedTestSuites
	 * @return
	 * @throws JavaModelException
	 */
	protected IType createTestSuiteFrame(String testSuiteName,
			ICompilationUnit testSuite, ICompilationUnit[] testClasses, String newTestSuiteElementName, HashSet<String> generatedTestSuites)
			throws JavaModelException {
		IType type = testSuite.getType(testSuiteName);

		String testclassList;
		
		if (type.exists()) {
			for (IAnnotation annotation : type.getAnnotations()) {
				if ("Suite.SuiteClasses".equals(annotation.getElementName())) {
					for (IMemberValuePair memberValuePairs : annotation.getMemberValuePairs()) {
						Object value = memberValuePairs.getValue();
						String suiteClassName;
						
						if (value instanceof Object[]) {
							for (Object testSuiteObject : (Object[])value) {
								suiteClassName = testSuiteObject.toString();
								if (suiteClassName.endsWith(TESTSUITE_PREFIX)) {
									generatedTestSuites.add(suiteClassName + ".class");
								}
								if (suiteClassName.equals(newTestSuiteElementName)) {
									return null;
								}
							}
						}
					}
					
				}
			}
			
			type.delete(true, null);
		}
		
		testclassList = createCommaSeparatedClassList(testClasses, generatedTestSuites);

		String azTestAnnotation = "@RunWith(Suite.class)\n@Suite.SuiteClasses(\n\n{"
				+ testclassList + "}\n)";
		type = testSuite.createType(azTestAnnotation
				+ "\npublic class " + testSuiteName + "{ // nix\n}", null,
				true, null);

		return type;
	}

	/**
	 * Creates a comma separated class list.
	 * 
	 * @param testClasses
	 * @param generatedTestSuites
	 * @return separated class list
	 */
	protected String createCommaSeparatedClassList(ICompilationUnit[] testClasses, HashSet<String> generatedTestSuites) {
		boolean first = true;
		StringBuilder sb = new StringBuilder();
		String testClassName;
		String testClassNameWithClassSuffix;

		for (ICompilationUnit testClass : testClasses) {
			testClassNameWithClassSuffix = testClass.getElementName().replace(".java",
					".class");
			testClassName = testClassNameWithClassSuffix.replace(".class", "");

			if (testClassName.equals(TESTSUITE_PREFIX) || !testClassName.endsWith("Test")) {
				continue;
			}

			if (!first) {
				sb.append(", ");
			} else {
				first = false;
			}

			sb.append(testClassNameWithClassSuffix);
		}
		
		for (String generatedTestSuite : generatedTestSuites) {
			if (!first) {
				sb.append(", ");
			} else {
				first = false;
			}
			
			sb.append(generatedTestSuite);
		}

		return sb.toString();
	}
}
